/**
 * SaltedHash.java
 * Represents a salted hash. Allows for a plain text password to be authenticated
 * with the known salt.
 * 
 * @author Ryan Senkbeil
 */

package com.msoe.core.users;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;

import com.google.gwt.user.client.rpc.IsSerializable;

import sun.misc.BASE64Encoder;

public class SaltedHash implements IsSerializable
{
	private final String hash;
    private final String salt;
    private final static int saltLength = 6;
    private static BASE64Encoder base64Encoder = new BASE64Encoder();

    private SaltedHash(String s, String h)
    {
        this.salt = s;
        this.hash = h;
    }

    /**
     * Creates a SHA-1 hash of the byte[] passed in.
     * @param data - The data to hash.
     * @return A byte array containing the bash.
     * @throws Exception
     */
    private static byte[] calculateHash(byte[] data)
    {
		try
		{
	    	// Create a new MessageDigest to create a new SHA1 hash
	    	MessageDigest md = MessageDigest.getInstance("SHA-1");
			
	    	md.update(data);
	    	
	    	// Return the hash of the data.
	    	return md.digest();
		}
		catch (NoSuchAlgorithmException e) 
		{
			e.printStackTrace();
		}
		
		return null;
    }

    /**
     * Calculates the hash of the salt and password passed in.
     * @param salt - The salt to use.
     * @param password - The password to user.
     * @return - A string containing the hash of the salt + hash.
     * @throws Exception
     */
    private static String calculateHash(String salt, String password)
    {
    	// returns a Base 64 string representing the hash.
    	return base64Encoder.encode(calculateHash(toByteArray(salt + password)));
    }

    /**
     * Creates a hash of the string passed in. A random salt is created.
     * @param password - The string to hash
     * @return - An instance of SaltedHash containing the hash and salt.
     * @throws Exception
     */
    public static SaltedHash create(String password)
    {
        String s = createSalt();
        
        return new SaltedHash(s, calculateHash(s, password));
    }

    /**
     * Creates a hash of the salt and password passed in.
     * @param salt
     * @param hash
     * @return An instance of SaltedHash containing the hash and salt.
     */
    public static SaltedHash create(String salt, String hash)
    {
        return new SaltedHash(salt, hash);
    }

    /**
     * Creates random bytes to be used when creating the salt.
     * @param len - The number of random bytes to create.
     * @return A byte[] containing random bytes.
     */
    private static byte[] createRandomBytes(int len)
    {
        byte[] data = new byte[len];
        
        Random random = new Random();
        random.nextBytes(data);

        return data;
    }

    /**
     * Creates a random salt.
     * @return A Base 64 encoded string containing the hash.
     */
    private static String createSalt()
    {
        return base64Encoder.encode(createRandomBytes(saltLength));
    }

    /**
     * Converts a String to a byte array.
     * @param s - The string to convert
     * @return
     * @throws UnsupportedEncodingException
     */
    private static byte[] toByteArray(String s)
    {
    	try 
    	{
			return s.getBytes("UTF-8");
		} 
    	catch (UnsupportedEncodingException e) 
    	{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    	
    	return null;
    }

    /**
     * Verifies a password against the known salt and salt/hash combination.
     * @param password - The password to check.
     * @return True if password is correct, otherwise false.
     * @throws Exception
     */
    public boolean verify(String password)
    {
        String str = calculateHash(this.salt, password);
        
        return this.hash.equals(str);
    }

    public String getHash()
    {
        return this.hash;
    }

    public String getSalt()
    {
        return this.salt;
    }
}
